import { BaseMiddleware, MiddlewareContext } from './base';
import { MessageProcessResult } from '@/core/message';
import { MessageMetrics, Logger } from '@/types';
import { createLogger } from '@/utils/logger';

/**
 * In-memory metrics storage
 */
export class InMemoryMetrics implements MessageMetrics {
  public sentCount = 0;
  public receivedCount = 0;
  public processedCount = 0;
  public failedCount = 0;
  public retryCount = 0;
  public averageProcessingTime = 0;
  public lastUpdated = Date.now();

  private processingTimes: number[] = [];
  private readonly maxSamples = 1000;

  recordSent(): void {
    this.sentCount++;
    this.lastUpdated = Date.now();
  }

  recordReceived(): void {
    this.receivedCount++;
    this.lastUpdated = Date.now();
  }

  recordProcessed(processingTime: number): void {
    this.processedCount++;
    this.processingTimes.push(processingTime);

    // Keep only the last N samples
    if (this.processingTimes.length > this.maxSamples) {
      this.processingTimes.shift();
    }

    // Recalculate average
    this.averageProcessingTime =
      this.processingTimes.reduce((sum, time) => sum + time, 0) / this.processingTimes.length;
    this.lastUpdated = Date.now();
  }

  recordFailed(): void {
    this.failedCount++;
    this.lastUpdated = Date.now();
  }

  recordRetry(): void {
    this.retryCount++;
    this.lastUpdated = Date.now();
  }

  getStats(): MessageMetrics {
    return {
      sentCount: this.sentCount,
      receivedCount: this.receivedCount,
      processedCount: this.processedCount,
      failedCount: this.failedCount,
      retryCount: this.retryCount,
      averageProcessingTime: this.averageProcessingTime,
      lastUpdated: this.lastUpdated,
    };
  }

  reset(): void {
    this.sentCount = 0;
    this.receivedCount = 0;
    this.processedCount = 0;
    this.failedCount = 0;
    this.retryCount = 0;
    this.averageProcessingTime = 0;
    this.processingTimes = [];
    this.lastUpdated = Date.now();
  }

  getDetailedStats(): {
    basic: MessageMetrics;
    percentiles: {
      p50: number;
      p95: number;
      p99: number;
    };
    rates: {
      successRate: number;
      failureRate: number;
      retryRate: number;
    };
  } {
    const sortedTimes = [...this.processingTimes].sort((a, b) => a - b);
    const totalMessages = this.processedCount + this.failedCount;

    return {
      basic: this.getStats(),
      percentiles: {
        p50: this.getPercentile(sortedTimes, 50),
        p95: this.getPercentile(sortedTimes, 95),
        p99: this.getPercentile(sortedTimes, 99),
      },
      rates: {
        successRate: totalMessages > 0 ? this.processedCount / totalMessages : 0,
        failureRate: totalMessages > 0 ? this.failedCount / totalMessages : 0,
        retryRate: totalMessages > 0 ? this.retryCount / totalMessages : 0,
      },
    };
  }

  private getPercentile(sortedArray: number[], percentile: number): number {
    if (sortedArray.length === 0) return 0;

    const index = Math.ceil((percentile / 100) * sortedArray.length) - 1;
    return sortedArray[Math.max(0, index)] || 0;
  }
}

/**
 * Metrics middleware for collecting message processing metrics
 */
export class MetricsMiddleware extends BaseMiddleware {
  private readonly metrics: InMemoryMetrics;
  private readonly reportInterval?: number;
  private reportTimer?: NodeJS.Timeout;

  constructor(metrics?: InMemoryMetrics, reportInterval?: number, logger?: Logger) {
    super('MetricsMiddleware', logger ?? createLogger('MetricsMiddleware'));
    this.metrics = metrics ?? new InMemoryMetrics();
    this.reportInterval = reportInterval;

    if (this.reportInterval && this.reportInterval > 0) {
      this.startReporting();
    }
  }

  async beforeSend(context: MiddlewareContext): Promise<MiddlewareContext> {
    // Record that we're about to send a message
    context.metadata.sendStartTime = Date.now();
    return context;
  }

  async afterSend(context: MiddlewareContext, result: boolean): Promise<void> {
    if (result) {
      this.metrics.recordSent();
    }
  }

  async beforeProcess(context: MiddlewareContext): Promise<MiddlewareContext> {
    this.metrics.recordReceived();
    context.metadata.processStartTime = Date.now();

    // Record retry if this is not the first attempt
    if (context.message.retryCount > 0) {
      this.metrics.recordRetry();
    }

    return context;
  }

  async afterProcess(context: MiddlewareContext, result: MessageProcessResult): Promise<void> {
    const processStartTime = context.metadata.processStartTime as number;
    const processingTime = Date.now() - processStartTime;

    if (result.success) {
      this.metrics.recordProcessed(processingTime);
    } else {
      this.metrics.recordFailed();
    }
  }

  async onError(context: MiddlewareContext, error: Error): Promise<void> {
    this.metrics.recordFailed();
  }

  /**
   * Get current metrics
   */
  getMetrics(): MessageMetrics {
    return this.metrics.getStats();
  }

  /**
   * Get detailed metrics with percentiles and rates
   */
  getDetailedMetrics(): ReturnType<InMemoryMetrics['getDetailedStats']> {
    return this.metrics.getDetailedStats();
  }

  /**
   * Reset all metrics
   */
  resetMetrics(): void {
    this.metrics.reset();
  }

  /**
   * Start periodic metrics reporting
   */
  private startReporting(): void {
    if (this.reportTimer) {
      clearInterval(this.reportTimer);
    }

    this.reportTimer = setInterval(() => {
      const stats = this.metrics.getDetailedStats();
      this.log('info', 'Metrics report', stats);
    }, this.reportInterval);
  }

  /**
   * Stop periodic metrics reporting
   */
  stopReporting(): void {
    if (this.reportTimer) {
      clearInterval(this.reportTimer);
      this.reportTimer = undefined;
    }
  }

  /**
   * Cleanup resources
   */
  destroy(): void {
    this.stopReporting();
  }
}

/**
 * Custom metrics middleware that allows custom metric collection
 */
export class CustomMetricsMiddleware extends BaseMiddleware {
  private readonly customMetrics = new Map<string, number>();
  private readonly customCounters = new Map<string, number>();
  private readonly customGauges = new Map<string, number>();
  private readonly customHistograms = new Map<string, number[]>();

  constructor(logger?: Logger) {
    super('CustomMetricsMiddleware', logger ?? createLogger('CustomMetricsMiddleware'));
  }

  /**
   * Increment a counter metric
   */
  incrementCounter(name: string, value = 1): void {
    const current = this.customCounters.get(name) || 0;
    this.customCounters.set(name, current + value);
  }

  /**
   * Set a gauge metric
   */
  setGauge(name: string, value: number): void {
    this.customGauges.set(name, value);
  }

  /**
   * Record a histogram value
   */
  recordHistogram(name: string, value: number): void {
    if (!this.customHistograms.has(name)) {
      this.customHistograms.set(name, []);
    }
    this.customHistograms.get(name)!.push(value);
  }

  /**
   * Set a custom metric
   */
  setMetric(name: string, value: number): void {
    this.customMetrics.set(name, value);
  }

  async beforeSend(context: MiddlewareContext): Promise<MiddlewareContext> {
    this.incrementCounter('messages.send.attempts');
    return context;
  }

  async afterSend(context: MiddlewareContext, result: boolean): Promise<void> {
    if (result) {
      this.incrementCounter('messages.send.success');
    } else {
      this.incrementCounter('messages.send.failed');
    }
  }

  async beforeProcess(context: MiddlewareContext): Promise<MiddlewareContext> {
    this.incrementCounter('messages.process.attempts');

    if (context.message.retryCount > 0) {
      this.incrementCounter('messages.retries');
    }

    return context;
  }

  async afterProcess(context: MiddlewareContext, result: MessageProcessResult): Promise<void> {
    const processStartTime = (context.metadata.processStartTime as number) || context.startTime;
    const processingTime = Date.now() - processStartTime;

    this.recordHistogram('messages.processing_time', processingTime);

    if (result.success) {
      this.incrementCounter('messages.process.success');
    } else {
      this.incrementCounter('messages.process.failed');
    }
  }

  async onError(context: MiddlewareContext, error: Error): Promise<void> {
    this.incrementCounter('messages.errors');
    this.incrementCounter(`messages.errors.${error.constructor.name}`);
  }

  /**
   * Get all custom metrics
   */
  getCustomMetrics(): {
    counters: Record<string, number>;
    gauges: Record<string, number>;
    histograms: Record<string, { count: number; sum: number; avg: number }>;
    metrics: Record<string, number>;
  } {
    const histograms: Record<string, { count: number; sum: number; avg: number }> = {};

    for (const [name, values] of this.customHistograms) {
      const sum = values.reduce((a, b) => a + b, 0);
      histograms[name] = {
        count: values.length,
        sum,
        avg: values.length > 0 ? sum / values.length : 0,
      };
    }

    return {
      counters: Object.fromEntries(this.customCounters),
      gauges: Object.fromEntries(this.customGauges),
      histograms,
      metrics: Object.fromEntries(this.customMetrics),
    };
  }

  /**
   * Reset all custom metrics
   */
  resetCustomMetrics(): void {
    this.customMetrics.clear();
    this.customCounters.clear();
    this.customGauges.clear();
    this.customHistograms.clear();
  }
}

/**
 * Utility functions for creating metrics middleware
 */
export const createMetricsMiddleware = (
  metrics?: InMemoryMetrics,
  reportInterval?: number,
  logger?: Logger
): MetricsMiddleware => {
  return new MetricsMiddleware(metrics, reportInterval, logger);
};

export const createCustomMetricsMiddleware = (logger?: Logger): CustomMetricsMiddleware => {
  return new CustomMetricsMiddleware(logger);
};

export const createInMemoryMetrics = (): InMemoryMetrics => {
  return new InMemoryMetrics();
};
